/*
 * Backend.java
 *
 * Access backend functions
 */

package net.kernub.kebcore.client;

import java.util.HashMap;
import java.util.Set;
import java.util.Iterator;
import java.util.ArrayList;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.Response;

public class Backend implements RequestCallback
{
	/*
	 * Interface to be implemented to get a callback when a request is processed
	 */
	public interface Callback
	{
		public void onReturn( boolean callError, JavaScriptObject object );
	};

	/*
	 * Internal class to handle a deferred request
	 */
	private class DeferredRequest implements Command
	{
		private boolean isCanceled = false;
		private String url = null;
		private String postArgs = null;
		private Callback callback = null;

		public DeferredRequest( final String url, final String postArgs, final Callback callback )
		{
			this.url = url;
			this.postArgs = postArgs;
			this.callback = callback;
		}

		public void cancel()
		{
			this.isCanceled = true;
		}

		public void execute()
		{
			Backend.instance.deferredRequests.remove( this );
			if( this.isCanceled ) return;
			Backend.instance.call( this.url, this.postArgs, this.callback );
		}
	};

	private static Backend instance = null;

	private int loadingStarted = 0;
	private LoadingHandler loadingHandler = null;

	private final ArrayList<DeferredRequest> deferredRequests = new ArrayList<DeferredRequest>();

	private final HashMap<Request, Callback> mapRequests = new HashMap<Request, Callback>();

	public Backend()
	{
		Log.create();
		Service.create();
	}

	public static void create()
	{
		if( Backend.instance == null )
			instance = new Backend();
	}

	public static Backend inst()
	{
		return Backend.instance;
	}

	public void setLoadingHandler( final LoadingHandler handler )
	{
		this.loadingHandler = handler;
	}

	private void startLoading()
	{
		if( this.loadingStarted == 0 && this.loadingHandler != null )
			this.loadingHandler.onStart();
		this.loadingStarted++;
	}

	private void stopLoading()
	{
		this.loadingStarted--;
		if( this.loadingStarted == 0 && this.loadingHandler != null )
			this.loadingHandler.onStop();
	}

	public void callDeferred( final String url, final String postArgs, final Callback callback )
	{
		DeferredRequest cmd = new DeferredRequest( url, postArgs, callback );
		this.deferredRequests.add( cmd );
		DeferredCommand.addCommand( cmd );
	}

	public void call( final String url, final String postArgs, final Callback callback )
	{
		Request request = null;
		RequestBuilder builder = null;
		
		if( postArgs==null )
			builder = new RequestBuilder( RequestBuilder.GET, url );
		else
		{
			builder = new RequestBuilder( RequestBuilder.POST, url );
			// if not specified, the Content-Type is 'text/plain; charset=utf-8', which isn't interpreted as post data by php
			builder.setHeader( "Content-Type", "application/x-www-form-urlencoded" );
		}

		try
		{
			request = builder.sendRequest( postArgs, this );
			this.mapRequests.put( request, callback );
			startLoading();
		}
		catch( RequestException e )
		{
			Log.error( e.getMessage() );
		}
	}

	/*
	 * cancelCalls()
	 * cancel all currently pending calls (deferred commands included)
	 * note: we can add new call/deferred calls right after, this won't interfer with the cancelation process.
	 */
	public void cancelCalls()
	{
		// cancel all deferred commands (they will unregister themself from the list)
		for( int i = 0; i < this.deferredRequests.size(); i++ )
		{
			this.deferredRequests.get(i).cancel();
		}

		// cancel all current pending request 
		// note: do not call the callback since GWT doesn't fire onError() neither onResponseReceived()
		Iterator<Request> iterReq = this.mapRequests.keySet().iterator();
		Request request = null;
		while( iterReq.hasNext() )
		{
			request = iterReq.next();
			request.cancel();
		}
		this.mapRequests.clear();
	}

	public void onError( final Request request, final Throwable exception )
	{
		this.stopLoading();
		Log.error( "Backend: request error: " + exception.getMessage() );

		final Callback callback = this.mapRequests.remove( request );
		callback.onReturn( true, null );
	}

	public void onResponseReceived( final Request request, final Response response )
	{
		//GWT.log( "Backend.onResponseReceived: " + response.getText(), null );
		this.stopLoading();
		final Callback callback = this.mapRequests.remove( request );

		// check status code
		int sc = response.getStatusCode();
		if( sc != Response.SC_OK )
		{
			// log properly
			Log.error( "Backend: request failed (check console), status code: "+sc+" - "+response.getStatusText() );
			Log.dbg( response.getText() );

			// fire event
			callback.onReturn( true, null );
			return;
		}

		// ok, we got a JSInputMessage then
		JSInputMessage message = null;
		try
		{
			message = JSInputMessage.fromJSON( response.getText() );
		}
		catch( Exception e )
		{
			Log.error( "Backend: request response is not an InputMessage" );
			Log.errorHtml( response.getText() );
			callback.onReturn( true, null );
			return;
		}

		if( message.hasLogs() )
		{
			for( int i = 0; i < message.getLogs().length(); i++ )
			{
				final JSLog log = message.getLogs().get(i);
				if( log.hasDebug() )
					Log.add( log.type(), log.message(), log.className(), log.methodName(), log.fileName(), log.lineNo() );
				else
					Log.add( log.type(), log.message() );
			}
		}

		if( message.hasObject() )
		{
			callback.onReturn( false, message.getObject() );
		}
		else
		{
			callback.onReturn( message.hasLogs(), null );
		}
	}

};

